import os
import sys
import logging
from TS_CAN import *
import pytest
from pytest_assume.plugin import assume
import crcmod
import math
from eprogress import LineProgress
from threading import Thread
import hashlib
import xml.etree.ElementTree as ET
import zipfile

class Download(Thread):
    def __init__(self,diag,diagfun,zip_path,erash_startaddress,erash_size):
        Thread.__init__(self)
        self.diag = diag
        self.diagfun = diagfun
        self.zip_path = zip_path
        self.eras_startaddress=erash_startaddress
        self.erash_size=erash_size
        self.startaddress=0x0
        self.size=0x0
        self.flag=True
        self.line_progress = 0
        self.state = False
        self.start()

    def get_path(self):
        root = os.getcwd()
        dirpath = os.path.join(root,r'temp')
        zFile = zipfile.ZipFile(self.zip_path, "r")
        self.app_filepath = ''
        self.app_filename = ''
        self.flash_filepath = ''
        self.flash_filename = ''
        self.xml_filepath = ''
        self.xml_filename = ''
        self.boot_filepath = ''
        self.boot_name = ''
        for fileM in zFile.namelist(): 
            zFile.extract(fileM, dirpath)
            if 'App' in fileM:
                self.app_filename=fileM
                self.app_filepath = os.path.join(dirpath,fileM)
            if 'APP' in fileM:
                self.app_filename=fileM
                self.app_filepath = os.path.join(dirpath,fileM)
            elif 'Flash' in fileM:
                self.flash_filename=fileM
                self.flash_filepath = os.path.join(dirpath,fileM)
            elif 'xml' in fileM:
                self.xml_filename=fileM
                self.xml_filepath = os.path.join(dirpath,fileM)
            else:
                self.boot_name=fileM
                self.boot_filepath = os.path.join(dirpath,fileM)

        zFile.close()
        return self.app_filepath,self.flash_filepath,self.xml_filepath,self.boot_filepath

    def get_xml_md5(self):
        tree = ET.parse(self.xml_filepath)
        root = tree.getroot()
        nodes = root.findall('MD5')
        xml_md5 = {}
        for node in nodes:
            xml_md5[node.attrib['FileName']] = node.text
        return xml_md5

    def get_file_md5(self,path_hex):
        with open(path_hex, 'rb') as file:
            data = file.read()
        diff_check = hashlib.md5()
        diff_check.update(data)
        md5_code = diff_check.hexdigest()
        return md5_code.upper()

    def check_md5(self):
        app_md5 = self.get_file_md5(self.app_filepath)
        flash_md5 = self.get_file_md5(self.flash_filepath)
        md5_dict = self.get_xml_md5()
        xml_app_md5 = md5_dict[self.app_filename]
        xml_flash_md5 = md5_dict[self.flash_filename]
        print('Config xml read md5 is: {}'.format(md5_dict))
        print('hex file read app_md5 is: {}'.format(app_md5))
        print('hex file read flash_md5 is: {}'.format(flash_md5))
        if (xml_app_md5 == app_md5)and(xml_flash_md5 == flash_md5):
            self.state = True
        return self.state

    def parse_file(self,file_path):
        self.file_type = os.path.splitext(file_path)[-1]
        self.binfile_handle = bincopy.BinFile()
        if(self.file_type == '.hex' or self.file_type == '.HEX'):
            with open(file_path, 'r') as fin:
                self.binfile_handle.add_ihex(fin.read())
        if(self.file_type == '.bin' or self.file_type == '.BIN'):
            with open(file_path, 'rb') as fin:
                self.binfile_handle.add_binary(fin.read())
        if(self.file_type == '.s19'):
            with open(file_path, 'r') as fin:
                self.binfile_handle.add_srec(fin.read())
        # logging.info(binfile_handle.segments())  #All data
        return self.binfile_handle.segments

    def download(self,path):
        download_data=[]
        self.parse_file(path)
        for index,segment in enumerate(self.binfile_handle.segments):
            if(self.file_type == '.bin' or self.file_type == '.BIN'):
                start_address = self.startaddress
                segment_size = self.size
            else:
                start_address = segment.minimum_address
                segment_size = segment.maximum_address - segment.minimum_address
                
            # 34 Server,Request download
            Request_download_CMD = [0x34,00,0x44] + \
                                    [(start_address>>24) & 0xFF] + [(start_address>>16) & 0xFF] + [(start_address>>8) & 0xFF] + [(start_address) & 0xFF] +\
                                    [(segment_size>>24) & 0xFF] + [(segment_size>>16) & 0xFF] + [(segment_size>>8) & 0xFF] + [(segment_size) & 0xFF]
            rep_value,rep_size = self.diag.req_and_res_can(Request_download_CMD,len(Request_download_CMD),enable_print= False)

            if rep_value[0:1] == [0x74]:
                lengthformatidentifier = rep_value[1]
                maxnumberofblocklength = rep_value[2]*256+rep_value[3]
                # 36 Server, start download
                if maxnumberofblocklength<0xfff:
                    numberofblockdatalength = maxnumberofblocklength-2
                    data_36_send_time = math.ceil(segment_size/numberofblockdatalength)
                    print('data_36_send_time is: ',data_36_send_time)
                    # line progress
                    line_progress = LineProgress(title='upgrade progress')
                    try:
                        for i in range(data_36_send_time):
                            self.progress_number = ((i+1)/data_36_send_time)*100
                            line_progress.update(self.progress_number)
                            blocksequencecounter = (i+1)%256
                            if i == (data_36_send_time - 1):
                                download_data_CMD = [0x36,blocksequencecounter]+list(segment.data)[i*numberofblockdatalength:]
                            else:
                                download_data_CMD = [0x36,blocksequencecounter]+list(segment.data)[i*numberofblockdatalength:((i+1)*numberofblockdatalength)]
                            rep_value,rep_size = self.diag.req_and_res_can(download_data_CMD,len(download_data_CMD),enable_print= False)
                            # print('i is: {}, rep_value is: {}, flag is: {}'.format(i,rep_value,flag))
                            if rep_value[0:1] != [0x76] or self.flag == False :
                                raise Exception('download termination')
                    except Exception as e:
                        print(e)
            else:
                pass
            # 37 Server, stop download
            download_data = download_data + list(segment.data)
            rep_value,rep_size = self.diag.req_and_res_can([0x37],1,enable_print= False)
            if rep_value[0:1] == [0x77]:
                pass
            else:
                print('37 server negative response!')
        crc_32=crcmod.predefined.mkCrcFun('crc-32')
        download_data_byte = bytearray(download_data)
        crc_value = crc_32(download_data_byte)
        return crc_value

    def seedkey(self,seed):
        KeyKValue = 0x0094
        key = [0,0,0,0]

        iSeed = seed[0] << 24|seed[1] << 16|seed[2] << 8 |seed[3]
        KeyResult = ((((iSeed >> 1) ^ iSeed) << 3) ^ (iSeed >> 2))
        STccKey = KeyResult ^ KeyKValue
        key[0] = ((STccKey)>>24) & 0xFF
        key[1] = ((STccKey)>>16) & 0xFF
        key[2] = ((STccKey)>>8) & 0xFF
        key[3] = (STccKey) & 0xFF
        return key

    def pre_update(self):
        try:
            '''预刷新步骤'''
            #step1: 扩展会话模式            
            uds_rep,datasize = self.diag.req_and_res_can([0x10,0x03],2)
            if uds_rep[0:2] == [0x50,0x03]:
                print('Enter extended session ok')
            else:
                print('Enter extended session fail')
            #step3: 检查预刷新条件
            uds_rep,datasize = self.diag.req_and_res_can([0x31,0x01,0x02,0x03],4)
            if uds_rep[0:5] == [0x71,0x01,0x02,0x03,0x00]:
                print('Check programming precondition ok')
            else:
                print('Check programming precondition fail')
            #step7: 安全算法          
            uds_rep,datasize = self.diag.req_and_res_can([0x27,0x01],2)
            if uds_rep[0:2] == [0x67,0x01]:
                print('Request seed ok')
            else:
                print('Request seed fail')
            seed = uds_rep[2:6]
            key = self.seedkey(seed)
            uds_rep,datasize = self.diag.req_and_res_can([0x27,0x02]+key,6)
            if uds_rep[0:2] == [0x67,0x02]:
                print('Send key level1 ok')
            else:
                print('Send key level1 fail')
            # #step5: 停止诊断服务
            # uds_rep,datasize = self.diagfun.req_and_res_can([0x85,0x82],2)
            # #step6: 通信使能接受，关闭发送
            # uds_rep,datasize = self.diagfun.req_and_res_can([0x28,0x11,0x01],3)
            #step8: 编程会话模式
            uds_rep,datasize = self.diag.req_and_res_can([0x10,0x02],2)
            if uds_rep[0:2] == [0x50,0x02]:
                print('Enter programming session ok')
            else:
                print('Enter programming session fail')
            #step9: 安全算法
            uds_rep,datasize = self.diag.req_and_res_can([0x27,0x01],2)
            if uds_rep[0:2] == [0x67,0x01]:
                print('Request seed ok')
            else:
                print('Request seed fail')
            seed = uds_rep[2:6]
            key = self.seedkey(seed)
            uds_rep,datasize = self.diag.req_and_res_can([0x27,0x02]+key,6)
            if uds_rep[0:2] == [0x67,0x02]:
                print('Send key level1 ok')
            else:
                print('Send key level1 fail')
        except:
            print('CAN communication Error!')

    # def boot_update(self):
    #     #step:10/11/12: 34、36、37服务更新flash Drive   
    #     check_crc_flash = self.download(self.flash_filepath)     
    #     #step13: 检查传输行列
    #     crc_flash=[0,0,0,0]
    #     crc_flash[0] = ((check_crc_flash)>>24) & 0xFF
    #     crc_flash[1] = ((check_crc_flash)>>16) & 0xFF
    #     crc_flash[2] = ((check_crc_flash)>>8) & 0xFF
    #     crc_flash[3] = (check_crc_flash) & 0xFF
    #     uds_rep,datasize = self.diag.req_and_res_can([0x31,0x01,0x02,0x02]+crc_flash,8)
    #     if uds_rep[0:5] == [0x71,0x01,0x02,0x02,0x00]:
    #         print('Check Flash transmit sequence ok')
    #     else:
    #         print('Check Flash transmit sequence fail')

    def flash_driver_update(self):
        #step:10/11/12: 34、36、37服务更新flash Drive   
        check_crc_flash = self.download(self.flash_filepath)     
        #step13: 检查传输行列
        crc_flash=[0,0,0,0]
        crc_flash[0] = ((check_crc_flash)>>24) & 0xFF
        crc_flash[1] = ((check_crc_flash)>>16) & 0xFF
        crc_flash[2] = ((check_crc_flash)>>8) & 0xFF
        crc_flash[3] = (check_crc_flash) & 0xFF
        uds_rep,datasize = self.diag.req_and_res_can([0x31,0x01,0x02,0x02]+crc_flash,8)
        if uds_rep[0:5] == [0x71,0x01,0x02,0x02,0x00]:
            print('Check Flash transmit sequence ok')
        else:
            print('Check Flash transmit sequence fail')

    def app_update(self):
        #step14: 擦除存储器程序
        uds_rep,_ = self.diag.req_and_res_can([0x31,0x01,0xFF,0x00,0x44]+self.eras_startaddress+self.erash_size,13)
        if uds_rep[0:5] == [0x71,0x01,0xFF,0x00,0x00]:
            print('Erase Memory ok')
        else:
            print('Erase Memory fail')
        #step15/16/17: 34、36、37服务下载程序
        check_crc_app = self.download(self.app_filepath) 
        #step18: 检查传输行列
        crc_app=[0,0,0,0]
        crc_app[0] = ((check_crc_app)>>24) & 0xFF
        crc_app[1] = ((check_crc_app)>>16) & 0xFF
        crc_app[2] = ((check_crc_app)>>8) & 0xFF
        crc_app[3] = (check_crc_app) & 0xFF
        uds_rep,datasize = self.diag.req_and_res_can([0x31,0x01,0x02,0x02]+crc_app,8)
        if uds_rep[0:5] == [0x71,0x01,0x02,0x02,0x00]:
            print('Check APP transmit sequence ok')
        else:
            print('Check APP transmit sequence fail')    
        #step20: 检查软硬件一致性
        uds_rep,datasize = self.diag.req_and_res_can([0x31,0x01,0xFF,0x01],4)
        if uds_rep[0:5] == [0x71,0x01,0xFF,0x01,0x00]:
            print('Check programming Dependencies ok')
        else:
            print('Check programming Dependencies fail')

    def post_update(self):
        #step21: 硬件重启
        uds_rep,datasize = self.diag.req_and_res_can([0x11,0x01],2)
        if uds_rep[0:2] == [0x51,0x01]:
            print('Hardware reset ok')
        else:
            print('Hardware reset fail')
        time.sleep(3)
        '''后刷新步骤'''
        #step22: 恢复通信
        uds_rep,datasize = self.diag.req_and_res_can([0x28,0x80,0x01],3)
        #step16-3: 开启DTC 
        uds_rep,datasize = self.diag.req_and_res_can([0x85,0x81],2)

    def run(self):
        self.get_path()
        stste = self.check_md5()
        if stste:
            self.pre_update()
            self.flash_driver_update()
            self.app_update()
            self.post_update()
        else:
            print('MD5 Check fail!')

    def set_stop(self,flag):
        self.flag = flag

if __name__ == '__main__':
    drive_type = 'USE_TS_TC1014_DEVICE'
    configure_baudrate = {'CAN_Type':'CANFD','ArbRate':500,'DataRate':2000,'TermResistor':True}
    '''初始化CAN'''
    project_rbs = TSCAN(drive_type,configure_baudrate)
    project_rbs.connect_application()
    diag = TSCAN_UDS(ASupportFD= 1, AMaxdlc=15, reqID=0x7F2,resID=0x7FA,AFctID=0x7DF,p2_timeout=4000)
    diagfun = TSCAN_UDS(reqID=0x7DF,resID=0x7FA,AFctID=0x7DF)
    '''选择刷写文件，判断条件，刷写'''
    path = r'F:\BYD\SW\App_4.00.05_Boot_1.3_CT_2.12.2.10_2.1.1.4.XCDII.zip'
    erash_startaddress = [0x00,0x05,0x00,0x00]
    erash_size = [0x00,0x08,0x4A,0x96]
    dl = Download(diag,diagfun,path,erash_startaddress,erash_size)
    # time.sleep(20)
    # print(dl.progress_number)
    # dl.set_stop(False)
    dl.join()
    '''刷写完成后删除文件及文件夹'''
    app_filepath,flash_filepath,xml_filepath,boot_filepath = dl.get_path()
    os.remove(app_filepath)
    os.remove(flash_filepath)
    os.remove(xml_filepath)
    os.remove(boot_filepath)
    (dirpath,filename) = os.path.split(app_filepath)
    os.rmdir(dirpath)
    '''关闭CAN'''
    project_rbs.dis_connect_application()
    diag.UDS_can_delete()
    diagfun.UDS_can_delete()
